#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define NUM 1024
#define OPT_NUM 64

#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPENT_REDIR 3
#define trimSpace(start) \
    do                   \
    {
while (isspace(*start))
    start++;
}
while (0)

int lastCode = 0;
int lastSig = 0;
char lineCommand[NUM];
char *myargv[OPT_NUM];

int redirTyoe = NONE_REDIR;
char *redirFile = NULL;

viod commandCheck(char *commands)
{
    assert(commands);
    char *start = commands;
    char *end = commands + strlen(commands);

    while (start < end)
    {
        if (*start == '>')
        {
            *start = '\0';
            start++;
            if (*start == '>')
            {
                // >>
                redirTyoe = APPENT_REDIR;
                start++;
            }
            else
            {
                // >
                redirTyoe = OUTPUT_REDIR;
            }
            trimSpace(start);
            redirFile = start;
            break;
        }
        else if (*start == '<')
        {
            *start = '\0';
            start++;
            // 空格一直走
            trimSpace(start);
            // 捕获文件
            redirTyoe = INPUT_REDIR;
            redirFile = start;
            break;
        }
        else
        {
            start++;
        }
    }
}

int main()
{
    while (1)
    {
        redirTyoe = NONE_REDIR;
        redirFile = NULL;
        errno = 0;

        // 打印输出提示符
        printf("用户名:%s@主机名:%s 当前路径# ", getenv("LOGNAME"), getenv("HOSTNAME"));
        fflush(stdout);

        // 获取用户输入
        char *s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
        assert(s != NULL);
        (void)s;

        // 清除最后一个/n
        lineCommand[strlen(lineCommand) - 1] = 0;

        // 字符串切割 ll -l -a
        myargv[0] = strtok(lineCommand, " ");
        int i = 1;

        // ls命令，则需要显示颜色，判断是ls，后面加上颜色指令，再继续往后切割
        if (myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
        {
            myargv[i++] = (char *)"--color=auto";
        }

        while (myargv[i++] = strtok(NULL, " "))
            ;

        // 重定向
        // >
        // <
        // >>
        commandCheck(lineCommand);

        // cd命令处理
        if (myargv[0] != NULL && strcmp(myargv[0], "cd"))
        {
            if (myargv[1] != NULL)
                chdir(myargv[1]);
            continue;
        }

        // echo
        if (myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
        {
            if (strcmp(myargv[1], "$?") == 0)
            {
                printf("%d,%d\n", lastCode, lastSig);
            }
            else
            {
                printf("%s\n", myargv[1]);
            }
            continue;
        }

        // 条件编译
#ifdef DEBUG
        for (int num = 0; myargv[num]; num++)
        {
            printf("%s\n", myargv[num]);
        }
#endif

        // 执行命令：一般让子进程来做
        pid_t id = fork();
        assert(id != -1);
        if (id == 0)
        {
            switch (redirTyoe)
            {
            case NONE_REDIR: // 什么都不做
                break;
            case INPUT_REDIR:
                int fd = open(redirFile, O_RDONLY);
                if (fd < 0)
                {
                    perror("open");
                    exit(errno);
                }
                dup2(fd,0);
                break;
            case OUTPUT_REDIR:
            case APPENT_REDIR:
                int flags = O_WRONLY | O_CREAT;
                if (redirTyoe == APPENT_REDIR)
                    flags |= O_APPEND;
                else
                    flags |= O_TRUNC;
                umask(0);
                int fd = open(redirFile, flags, 0666);
                if (fd < 0)
                {
                    perror("open");
                    exit(errno);
                }
                dup2(fd,1);
                break;
            default:
                printf("bug?\n");
                break;
            }

            execvp(myargv[0], myargv);
            exit(1);
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        assert(ret > 0);
        (void)ret;
        lastCode = ((status >> 8) & 0xFF);
        lastSig = (status & 0x7F);
    }
    return 0;
}